package com.streambolics.worseminton.data;

import java.util.Calendar;
import java.util.Date;

import javax.jdo.PersistenceManager;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.Inheritance;
import javax.jdo.annotations.InheritanceStrategy;
import javax.jdo.annotations.NotPersistent;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;

import com.streambolics.data.Person;
import com.streambolics.std.AnchorBuilder;
import com.streambolics.std.HtmlBuilder;
import com.streambolics.std.StringExtensions;
import com.streambolics.std.TextBuilder;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
@Inheritance(strategy = InheritanceStrategy.NEW_TABLE)
public class Event extends WorsemintonPersistentObject
{
    @Persistent
    Long                   ownerId;

    @Persistent
    Long                   locationId;

    @Persistent
    Boolean                published;

    @Persistent
    Boolean                canceled;

    @Persistent
    Date                   time;

    @Persistent
    Long                   generalGenerationCount;

    @Persistent
    Long                   guestGenerationCount;

    @Persistent
    private String         field11;

    @Persistent
    private String         field12;

    @Persistent
    private String         field13;

    @Persistent
    private String         field14;

    @Persistent
    private Date           time2;

    @Persistent
    private String         field21;

    @Persistent
    private String         field22;

    @Persistent
    private String         field23;

    @Persistent
    private String         field24;

    @Persistent
    private String         comment;

    @NotPersistent
    private Location       _location;

    @NotPersistent
    private Person         _owner;

    @NotPersistent
    private InvitationList _invitations;

    /**
     * Loads an event from the database, given its internal ID.
     * 
     * @param aDatabase
     * @param aId
     * @return
     */

    public static Event load (PersistenceManager aDatabase, long aId)
    {
        Event e = aDatabase.getObjectById (Event.class, aId);
        e.setDatabase (aDatabase);
        return e;
    }

    /**
     * Adds a guest to an event.
     * 
     * @param aLocationInterest
     *            The LocationInterest corresponding to the guest.
     */

    public void addGuest (LocationInterest aLocationInterest)
    {
        Invitation i = locateInvitationForGuest (aLocationInterest.getGuestId ());
        if (i == null)
        {
            i = Invitation.create (getDatabase (), getId (), aLocationInterest);
            getInvitations ().append (i);
        }
        i.setInvited (true);
    }

    /**
     * Utility that increments an internal generation count, indicating how many
     * changes have been performed on the general data of the event.
     * 
     * By comparing with the generation count in an event, it is possible to
     * determine whether the guest has received the latest update about that
     * event.
     */

    public void bumpGeneralGenerationCount ()
    {
        if (generalGenerationCount == null)
        {
            generalGenerationCount = 1L;
        }
        else
        {
            generalGenerationCount++;
        }
    }

    /**
     * Utility that increments an internal generation count, indicating how many
     * changes have been performed on the guest list of an event.
     * 
     * By comparing with the generation count in an event, it is possible to
     * determine whether the guest has received the latest update about that
     * event.
     */

    public void bumpGuestGenerationCount ()
    {
        if (guestGenerationCount == null)
        {
            guestGenerationCount = 1L;
        }
        else
        {
            guestGenerationCount++;
        }
    }

    /**
     * Generate HTML code with anchors to perform the basic operations on an
     * event.
     * 
     * @param aBuilder
     *            The HTML builder that will receive the generated code.
     */

    public void generateActionAnchors (AnchorBuilder aBuilder)
    {
        generateIdLink (aBuilder, "/go/EventCancel", "Cancel");
        generateIdLink (aBuilder, "/go/EventComment", "Comment");
    }

    public void generateGuestListHtml (HtmlBuilder aBuilder)
    {
        for (Invitation i : getInvitations ().getElements ())
        {
            i.generateGuestHtml (aBuilder);
        }
    }

    public void generateMailDescription (TextBuilder aBuilder)
    {
        Location l = getLocation ();
        aBuilder.addParagraph ("Event location: " + l.getName ());
        aBuilder.addParagraph ("Event time: " + l.dateToString (getTime ()));
        if (StringExtensions.isNotEmpty (l.getAddress ()))
        {
            aBuilder.addParagraph ("Address: " + l.getAddress ());
        }

        for (Invitation i : getInvitations ().getElements ())
        {
            aBuilder.addParagraph ("  " + i.getGuest ().getBestName () + ": " + i.getStatusText ());
        }
    }

    public void generateShowDetailsAnchor (AnchorBuilder aBuilder)
    {
        generateIdLink (aBuilder, "/go/Event", getDescription ());
    }

    /**
     * The main date of the event, in the form of a Calendar object with the
     * appropriate time zone.
     * 
     * @return A Calendar object with the date and time of the event, and the
     *         time zone of the location.
     */

    public Calendar getCalendar ()
    {
        Calendar c = getLocation ().createCalendar ();
        c.setTime (time);
        return c;
    }

    /**
     * The public comment of this event.
     * 
     * @return A string with the current comment on the event.
     */

    public String getComment ()
    {
        return comment == null ? "" : comment;
    }

    /**
     * The number of confirmed guests.
     * 
     * @return The number of invitations that were accepted.
     */

    public int getConfirmedGuestCount ()
    {
        int n = 0;
        for (Invitation i : getInvitations ().getElements ())
        {
            if (i.isAccepted ())
            {
                n++;
            }
        }
        return n;

    }

    /**
     * The number of reserved courts.
     * 
     * @return The number of named courts recorded in the event.
     */

    public int getCourtCount ()
    {
        return StringExtensions.countNonBlank (field11, field12, field13, field14, field21, field22, field23, field24);
    }

    /**
     * The description of the event.
     * 
     * @return A relatively short string describing the event.
     */

    public String getDescription ()
    {
        Location l = getLocation ();
        return l.dateToString (time) + " @ " + l.getName ();
    }

    public String getField11 ()
    {
        return field11;
    }

    public String getField12 ()
    {
        return field12;
    }

    public String getField13 ()
    {
        return field13;
    }

    public String getField14 ()
    {
        return field14;
    }

    public String getField21 ()
    {
        return field21;
    }

    public String getField22 ()
    {
        return field22;
    }

    public String getField23 ()
    {
        return field23;
    }

    public String getField24 ()
    {
        return field24;
    }

    public long getGeneralGenerationCount ()
    {
        return generalGenerationCount == null ? 0 : generalGenerationCount;
    }

    public long getGuestGenerationCount ()
    {
        return guestGenerationCount == null ? 0 : guestGenerationCount;
    }

    /**
     * The LocationInterest for a given guest.
     * 
     * @param aGuestId
     *            The internal ID of the guest.
     * @return The LocationInterest for that guest. The GuestId of the
     *         LocationInterest may be different than the one passed as
     *         parameter, if the guest has requested another e-mail address to
     *         be used.
     */

    public LocationInterest getInterest (long aGuestId)
    {
        return LocationInterest.access (getDatabase (), getId (), aGuestId);
        // TODO : follow indirections here
    }

    public LocationInterest getInterest (Person aGuest)
    {
        return getInterest (aGuest.getId ());
    }

    public InvitationList getInvitations ()
    {
        if (_invitations == null)
        {
            _invitations = InvitationList.load (getDatabase (), this);
        }
        return _invitations;
    }

    public Location getLocation ()
    {
        if (_location == null)
        {
            _location = Location.load (getDatabase (), getLocationId ());
        }
        return _location;
    }

    public long getLocationId ()
    {
        return locationId;
    }

    public Person getOwner ()
    {
        if (_owner == null)
        {
            _owner = Person.load (getDatabase (), ownerId);
        }
        return _owner;
    }

    public long getOwnerId ()
    {
        return ownerId;
    }

    /**
     * The price per guest, as a displayable string.
     * 
     * @return The price per guest, if it can be computed, a special string
     *         otherwise.
     */

    public String getPricePerGuestAsString ()
    {
        double t = getTotalPrice ();
        if (t > 0)
        {
            int n = getConfirmedGuestCount ();
            if (n > 0)
            {
                return String.format ("%.2f", t / n);
            }
        }
        return "---";
    }

    public Date getTime ()
    {
        return time;
    }

    public Date getTime2 ()
    {
        return time2;
    }

    /**
     * The total price for the event.
     * 
     * @return The total price for the event, computed from the price per court,
     *         and the number of reserved courts. If the price cannot be
     *         computed, 0 is returned.
     */

    public double getTotalPrice ()
    {
        return getCourtCount () * getLocation ().getCourtPrice ();
    }

    /**
     * The total price for the event, as a displayable string.
     * 
     * @return A string representing the total price, or a series of dashes if
     *         the price cannot be computed.
     */

    public String getTotalPriceAsString ()
    {
        double t = getTotalPrice ();
        if (t > 0)
        {
            return String.format ("%.2f", t);
        }
        return "---";
    }

    /**
     * Whether the event has been canceled.
     * 
     * @return True if the event has been explicitly cancelled, False otherwise.
     */

    public boolean isCanceled ()
    {
        return canceled == null ? false : canceled;
    }

    /**
     * Whether the event is in the future.
     * 
     * @return True if the main event date is in the future, false otherwise.
     */

    public boolean isFuture ()
    {
        return true;
    }

    /**
     * Whether the event is still pending. A pending event is worth sending by
     * email.
     * 
     * @return True if the event is still pending, false otherwise.
     */

    public boolean isPending ()
    {
        return isPublished () && !isCanceled () && isFuture ();
    }

    public boolean isPublished ()
    {
        return published == null ? false : published;
    }

    /**
     * Locates the invitation for a given guest.
     * 
     * @param aGuestId
     *            The guest for which an invitation is needed.
     * 
     * @return The Invitation for that guest, null if there is no invitation.
     * 
     * @note The indirections from the LocationInterest are not followed.
     */

    public Invitation locateInvitationForGuest (long aGuestId)
    {
        for (Invitation i : getInvitations ().getElements ())
        {
            if (i.getGuestId () == aGuestId)
            {
                return i;
            }
        }
        return null;
    }

    /**
     * Send all e-mails for the event.
     * 
     */

    public void sendEmails ()
    {
        for (Invitation i : getInvitations ().getElements ())
        {
            try
            {
                i.sendEmail ();
            }
            catch (Exception e)
            {
                i.setPrivateComment ("Unable to send mail - " + e.getClass ().getName () + " " + e.getMessage ());
                e.printStackTrace (System.err);
            }
        }
    }

    /**
     * Set the main date for the event.
     * 
     * @param aCalendar
     */

    public void setCalendar (Calendar aCalendar)
    {
        setTime (aCalendar.getTime ());
    }

    public void setCanceled (boolean aCanceled)
    {
        if (isCanceled () != aCanceled)
        {
            generalGenerationCount++;
            canceled = aCanceled;
        }
    }

    public void setComment (String comment)
    {
        this.comment = comment;
        bumpGeneralGenerationCount ();
    }

    public void setField11 (String aField11)
    {
        if (!aField11.equals (field11))
        {
            bumpGeneralGenerationCount ();
            field11 = aField11;
        }
    }

    public void setField12 (String aField12)
    {
        if (!aField12.equals (field12))
        {
            bumpGeneralGenerationCount ();
            field12 = aField12;
        }
    }

    public void setField13 (String aField13)
    {
        if (!aField13.equals (field13))
        {
            bumpGeneralGenerationCount ();
            field13 = aField13;
        }
    }

    public void setField14 (String aField14)
    {
        if (!aField14.equals (field14))
        {
            bumpGeneralGenerationCount ();
            field14 = aField14;
        }
    }

    public void setField21 (String aField21)
    {
        if (!aField21.equals (field21))
        {
            bumpGeneralGenerationCount ();
            field21 = aField21;
        }
    }

    public void setField22 (String aField22)
    {
        if (!aField22.equals (field22))
        {
            bumpGeneralGenerationCount ();
            field22 = aField22;
        }
    }

    public void setField23 (String aField23)
    {
        if (!aField23.equals (field23))
        {
            bumpGeneralGenerationCount ();
            field23 = aField23;
        }
    }

    public void setField24 (String aField24)
    {
        if (!aField24.equals (field24))
        {
            bumpGeneralGenerationCount ();
            field24 = aField24;
        }
    }

    public void setLocationId (long aLocationId)
    {
        locationId = aLocationId;
    }

    public void setOwnerId (long aOwnerId)
    {
        ownerId = aOwnerId;
    }

    public void setPublished (boolean aPublished)
    {
        if (isPublished () != aPublished)
        {
            bumpGeneralGenerationCount ();
            published = aPublished;
        }
    }

    public void setTime (Date aTime)
    {
        if (!aTime.equals (time))
        {
            bumpGeneralGenerationCount ();
            time = aTime;
        }
    }

    public void setTime2 (Date aTime2)
    {
        if (!aTime2.equals (time2))
        {
            bumpGeneralGenerationCount ();
            time2 = aTime2;
        }
    }

}
